﻿using System;
using System.Collections.Generic;
using System.Text;

namespace PFD
{
    public abstract class VertexBufferBase : GeometryArray, IVertexBuffer
    {
        public uint ColorOffset;
        public uint NormalOffset;
        public uint TextureSet1Offset;
        public uint TextureSet2Offset;

        protected bool bContainsNormals;
        protected bool bContainsColors;
        protected bool bContainsTextureSet1;
        protected bool bContainsTextureSet2;

        protected VertexElementType vertexElementType;
        public uint vertex_stride = 0;

        public uint stride { get { return vertex_stride; } }

        uint numVertices;

        public uint NumberOfVertices { get { return numVertices; } }

        public bool ColorsEnabled
        {
            get { return bContainsColors; }
            set { bContainsColors = value; }
        }
        public bool NormalsEnabled
        {
            get { return bContainsNormals; }
            set { bContainsNormals = value; }
        }
        public bool TextureSet1Enabled
        {
            get { return bContainsTextureSet1; }
            set { bContainsTextureSet1 = value; }
        }
        public bool TextureSet2Enabled
        {
            get { return bContainsTextureSet2; }
            set { bContainsTextureSet2 = value; }
        }

        public VertexBufferBase(uint numVerts, VertexElementType vet)
        {
            vertexElementType = vet;
            numVertices = numVerts;
            vertex_stride = 3;     // because of position vertex

            if ((vet & VertexElementType.Color) == VertexElementType.Color)
            {
                ColorOffset = vertex_stride;
                //  colors are a 4 dimensional vector
                vertex_stride += 4;
                bContainsColors = true;
            }
            else
                bContainsColors = false;

            if ((vet & VertexElementType.Normal) == VertexElementType.Normal)
            {
                NormalOffset = vertex_stride;
                //  Normals are a 3 dimensional vector
                vertex_stride += 3;
                bContainsNormals = true;
            }
            else
                bContainsNormals = false;

            if ((vet & VertexElementType.TextureSet1Coordinates) == VertexElementType.TextureSet1Coordinates)
            {
                TextureSet1Offset = vertex_stride;
                //  Texture coordinates are currently only 2 dimensional
                vertex_stride += 2;
                bContainsTextureSet1 = true;

                if ((vet & VertexElementType.TextureSet2Coordinates) == VertexElementType.TextureSet2Coordinates)
                {
                    TextureSet2Offset = vertex_stride;
                    //  yet another two-dimensional vector
                    vertex_stride += 2;
                    bContainsTextureSet2 = true;
                }
                else
                    bContainsTextureSet2 = false;
            }
            else
            {
                if ((vet & VertexElementType.TextureSet2Coordinates) == VertexElementType.TextureSet2Coordinates)
                {
                    //  this does not make sense to have NO texture set 1 
                    //  while a texture set 2 is defined
                    throw new ArgumentException("Not possible to have texture set 2 without texture set 1", "Vertex Buffer Setup");
                }
                bContainsTextureSet1 = false;
            }
        }

        protected abstract void Lock();

        protected abstract void Unlock();

        #region IVertexBuffer Members

        bool IVertexBuffer.AddVectors(float[] vertex)
        {
            Lock();
            if (vertex.Length != numVertices * 3)
                return false;

            for (int i = 0; i < vertex.Length; i++)
                array[(int)((i / 3) * vertex_stride + (i % 3))] = vertex[i];

            return true;
        }

        bool IVertexBuffer.AddColors(float[] colors)
        {
            Lock();
            if (!bContainsColors)
                throw new ArgumentException("Vertex buffer is not specified to contain Color components", "Vertex Buffer : Add colors");

            if (colors.Length != numVertices * 4)
                return false;

            for (int i = 0; i < colors.Length; i++)
                array[(int)((i / 4) * vertex_stride + (i % 4) + ColorOffset)] = colors[i];

            return true;
        }

        bool IVertexBuffer.AddNormals(float[] normals)
        {
            Lock();
            if (!bContainsNormals)
                throw new ArgumentException("Vertex buffer is not specified to contain Normal components", "Vertex Buffer : Add Normals");

            if (normals.Length != numVertices * 3)
                return false;

            for (int i = 0; i < normals.Length; i++)
                array[(int)((i / 3) * vertex_stride + (i % 3) + NormalOffset)] = normals[i];

            return true;
        }

        bool IVertexBuffer.AddTexCoords(uint n, float[] texcoords)
        {
            Lock();
            if (((n == 1) && (!bContainsTextureSet1)) ||
                 ((n == 2) && (!bContainsTextureSet2)))
                throw new ArgumentException("Vertex buffer is not specified to contain texture coordinates", "Vertex Buffer : Add Texture Coords");

            if ((n > 2) || (n < 1) || (texcoords.Length != numVertices * 2))
                return false;

            for (int i = 0; i < texcoords.Length; i++)
                array[(int)((i / 3) * vertex_stride + (i % 3) + TextureSet1Offset - 1 + n)] = texcoords[i];

            return true;
        }

        public float this[long itemno]
        {
            get
            {
                Lock();
                return array[itemno];
            }
            set
            {
                Lock();
                array[itemno] = value;
            }
        }

        public float this[long vertexno, VertexElementDimension element]
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                Lock();
                if ((vertexno >= array.Length) || (array.Length == 0))
                    throw new ArgumentOutOfRangeException("Parameter vertexno", "Illegel reference to vertex");
                switch (element)
                {
                    case    VertexElementDimension.X:
                        array[vertexno * stride] = value;
                        break;
                    case    VertexElementDimension.Y:
                        array[vertexno * stride + 1] = value;
                        break;
                    case    VertexElementDimension.Z:
                        array[vertexno * stride + 2] = value;
                        break;
                    case    VertexElementDimension.Nx  :
                        if (!bContainsNormals)
                            throw new ArgumentOutOfRangeException("element", "This vertex element (Normal) is not valid for this object");
                        array[vertexno * stride + NormalOffset] = value;
                        break;
                    case VertexElementDimension.Ny:
                        if (!bContainsNormals)
                            throw new ArgumentOutOfRangeException("element", "This vertex element (Normal) is not valid for this object");
                        array[vertexno * stride + NormalOffset + 1] = value;
                        break;
                    case VertexElementDimension.Nz:
                        if (!bContainsNormals)
                            throw new ArgumentOutOfRangeException("element", "This vertex element (Normal) is not valid for this object");
                        array[vertexno * stride + NormalOffset + 2] = value;
                        break;
                    case    VertexElementDimension.Alpha:
                        if (!bContainsColors)
                            throw new ArgumentOutOfRangeException("element", "This vertex element (Color) is not valid for this object");
                        array[vertexno * stride + ColorOffset + 3] = value;
                        break;
                    case VertexElementDimension.Red:
                        if (!bContainsColors)
                            throw new ArgumentOutOfRangeException("element", "This vertex element (Color) is not valid for this object");
                        array[vertexno * stride + ColorOffset] = value;
                        break;
                    case VertexElementDimension.Green:
                        if (!bContainsColors)
                            throw new ArgumentOutOfRangeException("element", "This vertex element (Color) is not valid for this object");
                        array[vertexno * stride + ColorOffset+1] = value;
                        break;
                    case VertexElementDimension.Blue:
                        if (!bContainsColors)
                            throw new ArgumentOutOfRangeException("element", "This vertex element (Color) is not valid for this object");
                        array[vertexno * stride + ColorOffset+2] = value;
                        break;
                    case VertexElementDimension.U1:
                        if (!bContainsTextureSet1)
                            throw new ArgumentOutOfRangeException("element", "This vertex element (TU1) is not valid for this object");
                        array[vertexno * stride + TextureSet1Offset] = value;
                        break;
                    case VertexElementDimension.V1:
                        if (!bContainsTextureSet1)
                            throw new ArgumentOutOfRangeException("element", "This vertex element (TU1) is not valid for this object");
                        array[vertexno * stride + TextureSet1Offset + 1] = value;
                        break;
                    case VertexElementDimension.U2:
                        if (!bContainsTextureSet1)
                            throw new ArgumentOutOfRangeException("element", "This vertex element (TU1) is not valid for this object");
                        array[vertexno * stride + TextureSet2Offset] = value;
                        break;
                    case VertexElementDimension.V2:
                        if (!bContainsTextureSet1)
                            throw new ArgumentOutOfRangeException("element", "This vertex element (TU1) is not valid for this object");
                        array[vertexno * stride + TextureSet2Offset + 1] = value;
                        break;
                }
            }
        }

        public long GetElementBufferIndex(VertexElementDimension ved, long index)
        {
            long nIdx = 0;
            switch (ved)
            {
                case VertexElementDimension.X: 
                    nIdx = index * stride; 
                    break;
                case VertexElementDimension.Y: 
                    nIdx = index * stride + 1; 
                    break;
                case VertexElementDimension.Z: 
                    nIdx = index * stride + 2; 
                    break;
                case VertexElementDimension.Nx: 
                    nIdx = index * stride + NormalOffset; 
                    break;
                case VertexElementDimension.Ny:
                    nIdx = index * stride + NormalOffset + 1;
                    break;
                case VertexElementDimension.Nz:
                    nIdx = index * stride + NormalOffset + 2;
                    break;
                case VertexElementDimension.Red:
                    nIdx = index * stride + ColorOffset;
                    break;
                case VertexElementDimension.Green:
                    nIdx = index * stride + ColorOffset + 1;
                    break;
                case VertexElementDimension.Blue:
                    nIdx = index * stride + ColorOffset + 2;
                    break;
                case VertexElementDimension.Alpha:
                    nIdx = index * stride + ColorOffset + 3;
                    break;
                case VertexElementDimension.U1:
                    nIdx = index * stride + TextureSet1Offset;
                    break;
                case VertexElementDimension.V1:
                    nIdx = index * stride + TextureSet1Offset + 1;
                    break;
                case VertexElementDimension.U2:
                    nIdx = index * stride + TextureSet2Offset;
                    break;
                case VertexElementDimension.V2:
                    nIdx = index * stride + TextureSet2Offset + 1;
                    break;
                default:
                    throw new ArgumentOutOfRangeException("Unknown index query", "VertexBufferOGL");

            }
            return nIdx;
        }

        public abstract bool RealizeVertexBuffer(IRenderDevice rd, bool AllowMultitexture, uint selectedTexture);

        #endregion

        #region IDisposable Members

        public abstract void Dispose();

        #endregion

        protected abstract bool CheckError(string msg);

    }
}
